from abc import ABC, abstractmethod
from data import *
from utils import *
from env import *
import enum
import threading
import time


class TaskError(BaseException):
    def __init__(self, msg) -> None:
        self.msg = msg

    def to_string(self) -> str:
        return self.msg

    def __str__(self) -> str:
        return self.to_string()


class Task(ABC):
    @abstractmethod
    def name(self) -> str:
        pass

    @abstractmethod
    def run(self):
        pass


class TimedTask:
    def __init__(self, time: float, task: Task):
        self.time = time
        self.task = task

    def name(self) -> str:
        return f"{int(self.time - time.time())}s: {self.task.name()}"


class TaskQueueState(enum.Enum):
    Finished = 0
    Running = 1
    Stoping = 2


@singleton
class TaskQueue:
    def __init__(self):
        self.queue = []
        self.running = TaskQueueState.Running
        self.thread = threading.Thread(target=self.main)
        self.thread.start()
        self.doneTasks: list[str] = []
        self.taskStack: list[str] = []

    def postTask(self, task: Task):
        self.queue.append(task)

    def main(self):
        while self.running != TaskQueueState.Finished:
            time.sleep(0.0167)
            if self.running == TaskQueueState.Running:
                if self.queue:
                    start_time = time.time()
                    task = self.queue.pop(0)
                    match task:
                        case TimedTask():
                            if task.time > time.time():
                                self.queue.append(task)
                                self.updateUI()
                                continue
                            task = task.task
                        case Task():
                            pass
                        case _:
                            assert False
                    try:
                        adb = EnsureADB()
                        adb.start_record()
                        self.runTask(task)
                    except TaskError as e:
                        print(f"{self.timeFormat(start_time)}: {e.to_string()}")
                    if adb:
                        adb.stop_record()
                    self.doneTasks.append(
                        f"{self.timeFormat(start_time)} +{int((time.time()-start_time)//0.001)*0.001}s: {task.name()}"
                    )
                    self.updateUI()
                    continue

    def timeFormat(self, seconds: float = time.time()) -> str:
        return time.strftime(
            f"%Y-%m-%d %H:%M:%S.{int((seconds-int(seconds))*1000):03d}",
            time.localtime(seconds),
        )

    def updateUI(self):
        strs = self.doneTasks.copy()
        if self.taskStack:
            stack = [">".join(self.taskStack)]
            print(f"{self.timeFormat()}: {stack}")
            strs += stack
        strs += [task.name() for task in self.queue]
        time.sleep(0.02)
        ToolsData().tasks.set(strs)

    def runTask(self, task: Task):
        self.taskStack.append(task.name())
        self.updateUI()
        task.run()
        self.taskStack.pop()
        self.updateUI()


def NoADBDeviceError():
    return TaskError("没有找到可用的ADB设备")


def TooManyADBDeviceError():
    return TaskError("过多可用的ADB设备")


def Bug(msg):
    return TaskError(f"BUG:{msg}")


def EnsureADB() -> ADBEnv:
    adb = ADBEnv()
    if adb.is_connected():
        return adb
    devices = adb.devices()
    if len(devices) == 0:
        raise NoADBDeviceError()
    if len(devices) == 1:
        adb.connect(devices[0])
        if adb.is_connected():
            return adb
    raise TooManyADBDeviceError()


def EnsureHomeEnv() -> None:
    adb = EnsureADB()
    for _ in range(5):
        if adb.exists("ui_menu2.png"):
            break
        adb.click_outside()


def EnsureFudiEnv() -> None:
    adb = EnsureADB()
    if adb.exists("ui_explore.png"):
        return
    for _ in range(10):
        EnsureHomeEnv()
        if not adb.click_buttons(["ui_menu2.png"]):
            continue
        if not adb.click_buttons(["ui_fudi.png"]):
            continue
        if not adb.click_buttons_utils_exist(
            ["button_explore.png", "state_get.png"], "ui_explore.png"
        ):
            continue
        if adb.exists_without("ui_explore.png", "state_get.png"):
            # 有前往
            return
    raise Bug("找不到前往")


def auto_run_without_error(cond, doing, result):
    for _ in range(6):
        if result():
            return True
        if not cond():
            return False
        doing()
        if result():
            return True
    return False


def auto_run(cond, doing, result):
    while True:
        if auto_run_without_error(cond, doing, result):
            return True
        adb = EnsureADB()
        if adb.exists("state_get.png"):
            adb.click_buttons(["state_get.png"])
        else:
            return False
        continue


def auto_run_raise(cond, doing, result, error):
    if not auto_run(cond, doing, result):
        raise error


class FudiExploreTask(Task):
    def __init__(self):
        self.data = FudiScriptData()
        self.round_number = self.data.round_number.get()

    def name(self) -> str:
        return f"福地探寻 {self.round_number}"

    def run(self):
        EnsureFudiEnv()
        adb = EnsureADB()
        self.data.status.set("开始探寻")
        self.data.round_number.set(self.data.round_number.get() + 1)
        self.collect(0)
        self.collect(1)
        self.collect(2)
        self.collect(3)
        self.collect(4)
        self.collect(5)
        self.collect(6)
        self.collect(7)
        if auto_run(
            lambda: adb.exists("ui_explore.png"),
            lambda: adb.click_buttons(["button_refresh.png"]),
            lambda: not adb.exists("button_refresh.png"),
        ):
            self.collect(0)
            self.collect(1)
            self.collect(2)
            self.collect(3)
            self.collect(4)
            self.collect(5)
            self.collect(6)
            self.collect(7)
        TaskQueue().postTask(TimedTask(time.time() + 300, FudiExploreTask()))

    def collect(self, which):
        adb = EnsureADB()
        if which >= 6:
            auto_run_raise(
                lambda: adb.exists("ui_explore.png"),
                lambda: adb.swipe(500, 1700, 500, 500),
                lambda: len(adb.find_all("button_goto.png")) >= 1
                and len(adb.find_all("button_goto.png")) <= 5,
                Bug("划了之后goto数量不对"),
            )
            result = [None, None, None]
        else:
            result = []
        result = result + sorted(
            adb.find_all("button_goto.png"), key=lambda x: x[0] + x[1]
        )
        if which < len(result):
            result = result[which]
        else:
            return
        auto_run_raise(
            lambda: adb.exists("ui_explore.png"),
            lambda: adb.input_tap(result[0], result[1]),
            lambda: adb.exists("button_explore.png"),
            Bug("点了之后没有出现button_explore"),
        )
        fudi_items = [
            {
                "cond": self.data.checked_tz5.get(),
                "item": "item_tz5.png",
                "number": self.data.tz5_number,
            },
            {
                "cond": self.data.checked_tz4.get(),
                "item": "item_tz4.png",
                "number": self.data.tz4_number,
            },
            {
                "cond": self.data.checked_tz3.get(),
                "item": "item_tz3.png",
                "number": self.data.tz3_number,
            },
        ]
        for fudi_item in fudi_items:
            if not fudi_item["cond"]:
                continue
            poses = adb.find_all(fudi_item["item"], threshold=0.95)
            for pos in poses:
                auto_run_raise(
                    lambda: adb.exists("button_explore.png"),
                    lambda: adb.input_tap(pos[0], pos[1]),
                    lambda: adb.exists("ui_fudi_item.png"),
                    Bug("点了之后没有出现ui_fudi_item"),
                )
                if adb.exists("state_collecting.png"):
                    auto_run_raise(
                        lambda: adb.exists("state_collecting.png"),
                        lambda: adb.click_outside(),
                        lambda: adb.exists("button_explore.png"),
                        Bug("点外面之后没有出现button_explore"),
                    )
                else:
                    if auto_run(
                        lambda: adb.exists("ui_fudi_item.png"),
                        lambda: adb.click_buttons(["button_collect.png"]),
                        lambda: not adb.exists("ui_fudi_item.png") and adb.exists("button_explore.png"),
                    ):
                        fudi_item["number"].set(fudi_item["number"].get() + 1)
                    else:
                        auto_run_raise(
                            lambda: adb.exists("ui_fudi_item.png"),
                            lambda: adb.click_outside(),
                            lambda: not adb.exists("ui_fudi_item.png") and adb.exists("button_explore.png"),
                            Bug("点外面之后没有出现button_explore"),
                        )
                    auto_run_raise(
                        lambda: adb.exists("button_explore.png"),
                        lambda: adb.click_outside(),
                        lambda: adb.exists("ui_explore.png"),
                        Bug("点外面之后没有出现ui_explore"),
                    )
                    return
        auto_run_raise(
            lambda: adb.exists("button_explore.png"),
            lambda: adb.click_outside(),
            lambda: adb.exists("ui_explore.png"),
            Bug("点外面之后没有出现ui_explore"),
        )
